#include "my_disk_cache.h"
#include "comm/my_syscall.h"
#include "comm/my_file_utils.h"

static void
my_disk_cache_open_done(my_disk_cache_t *disk_cache, my_bool_t success) {
    if (!success) {
        if (disk_cache->fd != INVALID_FD) {
            my_syscall_close(disk_cache->fd);
        }

        if (disk_cache->open_stats) {
            my_free(disk_cache->open_stats);
            disk_cache->open_stats = NULL;
        }

        if (disk_cache->volumes) {
            my_free(disk_cache->volumes);
            disk_cache->volumes = NULL;
        }
    }

    disk_cache->done_cb(disk_cache, success);
}

void
my_disk_cache_volume_open_done(my_disk_volume_t *volume, my_bool_t success) {
    my_disk_cache_t *disk_cache = volume->ctx;
    *(disk_cache->open_stats + volume->id) = success;
    int64_t res = my_atomic_int_increment_and_fetch(&disk_cache->ndone, 1);
    if (res != disk_cache->nvolumes) {
        return;
    }

    // some volumes may fail to open.
    // here we always return true, because disk_cache already opened.
    my_disk_cache_open_done(disk_cache, MY_TRUE);
}

static my_bool_t
my_disk_cache_open_or_create_file_helper(my_disk_cache_t *disk_cache) {
    my_bool_t need_create = MY_FALSE;
    if (!my_file_exists(disk_cache->file_path)) {
        need_create = MY_TRUE;
    }

    mode_t mode = O_DIRECT|O_RDWR;
    if (need_create) {
        mode |= O_CREAT;
    }

    my_syscall_result_t res = my_syscall_open(disk_cache->file_path, mode, 0644);
    if (res.eno != 0) {
        MY_LOG_WARN("disk_cache", "open %s failed, mode: %lld, errno: %d", disk_cache->file_path,
            mode, res.eno);
        return MY_FALSE;
    }

    disk_cache->fd = (int64_t)res.res;

    res = my_syscall_ftruncate(disk_cache->fd, disk_cache->except_size);
    if (res.eno != 0) {
        MY_LOG_WARN("disk_cache", "ftruncate %s failed, err: %d", disk_cache->file_path, res.eno);
        return MY_FALSE;
    }

    return MY_TRUE;
}

static my_bool_t
my_disk_cache_open_or_create_file(my_disk_cache_t *disk_cache) {
    if (disk_cache->except_size <= 0) {
        MY_LOG_FATAL0("disk_cache", "except size must be specified in normal file device");
    }

    if (my_disk_cache_open_or_create_file_helper(disk_cache)) {
        return MY_TRUE;
    }

    // source release
    if (disk_cache->fd != INVALID_FD) {
        my_syscall_close(disk_cache->fd);
        disk_cache->fd = INVALID_FD;
    }

    return MY_FALSE;
}


static my_bool_t
my_disk_cache_open_block_device_helper(my_disk_cache_t *disk_cache) {
    my_syscall_result_t res = my_syscall_open(disk_cache->file_path, O_DIRECT|O_RDWR, 0644);
    if (res.eno != 0) {
        MY_LOG_WARN("disk_cache", "open %s failed, eno: %d", disk_cache->file_path, res.eno);
        return MY_FALSE;   
    }

    disk_cache->fd = (int)res.res;

    int64_t block_size;
    res = my_syscall_ioctl(disk_cache->fd, BLKGETSIZE64, &block_size);
    if (res.eno != 0) {
        MY_LOG_WARN("disk_cache", "get %s block device size failed, eno: %d",
            disk_cache->file_path, res.eno);
        return MY_FALSE;
    }

    if (disk_cache->except_size <= 0) {
        disk_cache->except_size = block_size;
    }

    if (disk_cache->except_size > block_size) {
        MY_LOG_FATAL("disk_cache", "the %s block device size is %d, but except size is %d, "
            "you need check your configuartion", disk_cache->file_path, block_size,
            disk_cache->except_size);
    }

    MY_LOG_DEBUG("disk_cache", "open %s device success", disk_cache->file_path);
    return MY_TRUE;
}

static my_bool_t
my_disk_cache_open_block_device(my_disk_cache_t *disk_cache) {
    if (my_disk_cache_open_block_device_helper(disk_cache)) {
        return MY_TRUE;
    }

    // source release
    if (disk_cache->fd != INVALID_FD) {
        my_syscall_close(disk_cache->fd);
        disk_cache->fd = INVALID_FD;
    }

    return MY_FALSE;
}

static my_bool_t
my_disk_cache_open_device(my_disk_cache_t *disk_cache) {
    if (my_device_is_block(disk_cache->file_path)) {
        return my_disk_cache_open_block_device(disk_cache);
    }

    return my_disk_cache_open_or_create_file(disk_cache);
}

void
my_disk_cache_open(my_disk_cache_t *disk_cache, int level,
    const char *file_path, int64_t except_size,
    my_bool_t create_new, my_disk_volume_err_cb err_cb,
    my_disk_cache_open_done_cb done_cb, void *ctx) {

    disk_cache->level = level;
    disk_cache->file_path = file_path;
    disk_cache->except_size = except_size;
    disk_cache->done_cb = done_cb;
    disk_cache->ctx = ctx;

    my_bool_t res = my_disk_cache_open_device(disk_cache);
    if (!res) {
        MY_LOG_WARN("disk_cache", "open %s device failed", disk_cache->file_path);
        return my_disk_cache_open_done(disk_cache, MY_FALSE);
    }

    int volume_num = g_cache_conf.storage_cache.volume_num;
    int64_t volume_size = disk_cache->except_size / volume_num;
    volume_size = MY_ALIGN_PAGE_SIZE(volume_size);
    if (volume_size > MY_DISK_MAX_VOLUME_SIZE ||
        volume_size < (MY_DISK_VOLUME_MIN_DATA_SPACE_SIZE + MY_DISK_ONE_GROUP_SIZE)) {
        MY_LOG_FATAL("disk_cache", "volume size %lld must less than %lld and greater than %lld",
            MY_DISK_MAX_VOLUME_SIZE, MY_DISK_VOLUME_MIN_DATA_SPACE_SIZE + MY_DISK_ONE_GROUP_SIZE);
    }

    disk_cache->nvolumes = volume_num;
    disk_cache->volumes = my_malloc(sizeof(my_disk_volume_t) * disk_cache->nvolumes);
    disk_cache->open_stats = my_malloc(sizeof(my_disk_volume_t) * disk_cache->nvolumes);
    for (int i = 0; i < disk_cache->nvolumes; i++) {
        int64_t start_offset = i * volume_size;
        my_disk_volume_init(disk_cache->volumes+i, disk_cache->level,
            err_cb, my_disk_cache_volume_open_done, disk_cache);
        *(disk_cache->open_stats + i) = MY_FALSE;
        my_disk_volume_open(disk_cache->volumes+i, file_path, i, disk_cache->fd, start_offset,
            volume_size, create_new);
    }
}

void
my_disk_cache_notify_close(my_disk_cache_t *disk_cache,
    my_bool_t only_close_write) {

    for (int i = 0; i < disk_cache->nvolumes; i++) {
        my_disk_volume_notify_close(disk_cache->volumes + i, only_close_write);
    }
}